package network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Vector;

import network.Packet.packettype;
import network.Packet_Message.MessageType;

import shared.Vec2i;
import vmo.Game;

public class Client {

	enum clientstate {CONNECTING,CONNECTED,UNCONNECTED};
	float m_clock;
	Thread m_async;
	Receiver m_receiver;
	int m_lastsent,m_lastreceived;
	DatagramSocket m_outsocket;
	clientstate m_connected;
	InetAddress m_serverIP;
	Vector <Request> m_requests;
	short m_instanceID;
	public Client()
	{
		m_connected=clientstate.CONNECTING;
		m_clock=0;
		//setup ports for client
		m_receiver=new Receiver(781);
		
		m_async=new Thread(m_receiver);	
		m_async.start();
		try {
			m_outsocket=new DatagramSocket();
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		m_lastsent=0; m_lastreceived=0;
		m_requests=new Vector <Request>();
		
	}
	
	public void Discard()
	{
		m_async.interrupt();
		m_receiver.interrupt();
		m_receiver.Discard();
		m_outsocket.close();	
	}	
	
	public Packet getPacket()
	{
		
		if (m_receiver.PacketWaiting())
		{
			Packet p=m_receiver.TakePacket();
			if (p!=null)
			{
				PacketProcessing(p);
				m_lastreceived=(int)(m_clock*60);
				return p;
					
			}

		}
		else
		{
			return null;
			
		}
		return null;
	}

	public void Update(float dt)
	{
		m_clock+=dt/Game.m_tickspersecond;
		if (m_connected==clientstate.CONNECTED)
		{
			Keepalive();
			TimeOut();
			RequestControl();
		}
		if (m_connected==clientstate.CONNECTING)
		{
			if ((float)m_lastsent/60 <m_clock-5)
			{
				m_connected=clientstate.UNCONNECTED;
			}
		}
	}
	
	void PurgeRequests(Vec2i p)
	{
		if (m_requests.size()>0)
		{
			
		}	
	}
	
	void RequestControl()
	{
		//time out and retransmit requests
		if (m_requests.size()>0)
		{
			for (int i=0;i<m_requests.size();i++)
			{
				if (m_requests.get(i).getTimestamp()/60<m_clock-2)
				{
					Request request=m_requests.get(i);
					//m_requests.remove(i);
					if (request.CanRefresh())
					{
						int t=(int) (60*m_clock);
						request.setTimestamp(t);
						Packet_Request req=new Packet_Request(request.getInstance(), (short)request.getEntity(), (short)request.getX(), (short)request.getY(), (short)request.getZ(), m_serverIP, (int) (m_clock*60));
						req.m_bitfield=request.getBitfield();
						Transmit(req, m_serverIP);
					}
					else
					{
						m_requests.remove(i);
					}
				}
			}
		}
	}
	
	boolean RequestAbsent(int x, int y, int z, int entity)
	{
		if (m_requests.size()>0)
		{
			for (int i=0;i<m_requests.size();i++)
			{
				if (m_requests.get(i).getX()==x &&
					m_requests.get(i).getY()==y &&
					m_requests.get(i).getZ()==z &&
					m_requests.get(i).getEntity()==entity)
				{
					return false;
				}
			}
		}
		return true;
	}
	
	public void TransmitRequest(int x, int y, int z, int entity)
	{
		if (RequestAbsent(x,y,z,entity))
		{
			Request request=new Request((int) (m_clock*60),x, y, z, entity, m_instanceID);
			m_instanceID++;

			SendRequest(request);
		}	
	}
	
	void SendRequest(Request request)
	{
		m_requests.add(request);		
		Packet_Request req=new Packet_Request(request.getInstance(), (short)request.getEntity(), (short)request.getX(), (short)request.getY(), (short)request.getZ(), m_serverIP, (int) (m_clock*60));
		Transmit(req, m_serverIP);
	}
	
	void Keepalive()
	{
		//if last sent is more than 2 seconds ago, send keep alive
		if ((float)m_lastsent/60 <m_clock-2)
		{
			//send keepalive messsage
			Packet_Alive pa=new Packet_Alive((int)m_clock*60);
			Transmit(pa,m_serverIP);
		}
	}
	
	void TimeOut()
	{
		//if last received more than 5 seconds, end connection
		if ((float)m_lastreceived/60 < m_clock-5)
		{
			m_connected=clientstate.UNCONNECTED;
		}
	}
	
	public clientstate getConnected()
	{
		return m_connected;
	}
	
	void PacketProcessing(Packet packet)
	{
		//check if the packet is an acknowledgement, if so then change the clock to the server timestamp
		m_lastreceived=(int) (m_clock*60);
		switch (packet.getType())
		{
			case MESSAGE:
				handleMessage((Packet_Message)packet);
				break;
			case PLAYER:
				handlePing(packet);
			break;
			case ALIVE:
				handleAlive(packet);
				break;
			case CHUNK:
				handleChunk((Packet_Chunk) packet);
				break;
		
		}
		
	}	
	
	void handleChunk(Packet_Chunk packet)
	{
		if (m_requests.size()>0)
		{
			for (int i=0;i<m_requests.size();i++)
			{
				if (m_requests.get(i).getInstance()==packet.getInstance())
				{
					m_requests.get(i).AddPacket(packet);
					break;
				}
			}
		}
	}
	
	void handleAlive(Packet packet)
	{
		boolean filler=true;
		
	}
	
	void handlePing(Packet packet)
	{
		m_clock=(float)packet.getTimestamp()/60;	

		m_connected=clientstate.CONNECTED;
		m_lastreceived=packet.getTimestamp();
	}
	
	void handleMessage(Packet_Message packet)
	{
		switch (packet.m_msgtype)
		{
		case ACKNOWLEDGEMENT:
			//m_clock=packet.getTimestamp();
			Packet p=new Packet(packettype.PING,packet.getTimestamp(),packet.getAddress());
			Transmit(p,p.getAddress());
			break;
		
		}

		m_lastreceived=packet.getTimestamp();
	}
	
	void Transmit(Packet p,InetAddress address)
	{
		m_lastsent=(int) (m_clock*60);
		DatagramPacket dp=p.Encode(address,9876);
		try {
			m_outsocket.send(dp);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}
	
	public Request getCompleteRequest()
	{
		if (m_requests.size()>0)
		{
			for (int i=0;i<m_requests.size();i++)
			{
				if (m_requests.get(i).getComplete())
				{
					Request request=m_requests.get(i);
					m_requests.remove(i);
					return request;
				}
			}
		}
		return null;
	}
	
	public void Connect(InetAddress address,String name)
	{
		//build a handshake and send it to the 
		//designated ip address
		Packet_Message p=new Packet_Message(name, 0, MessageType.HANDSHAKE);
		Transmit(p,address);	
		m_serverIP=address;
	}
	
}
